"""
cloudsync.py - 城通网盘WebDAV同步管理库
版本: 2.2 简化版
命名: CloudSync (云同步)
描述: 简化版的城通网盘WebDAV操作库，针对小文件(<64MB)优化，使用直接上传方式
"""

import requests
import os
import json
import hashlib
from typing import Optional, Dict, Any, List
from urllib.parse import urljoin
import base64
from pathlib import Path
import time
import xml.etree.ElementTree as ET


class CloudSync:
    """
    城通网盘WebDAV同步客户端
    
    功能特性:
    - 简化的登录认证
    - 直接文件上传下载(针对<64MB文件优化)
    - 目录列表查看
    - 文件存在性检查
    - 文件信息获取
    - 简单高效，无复杂分块逻辑
    """
    
    def __init__(self, base_url: str = "https://webdav.ctfile.com/ws1164253"):
        """
        初始化CloudSync客户端
        
        Args:
            base_url: WebDAV服务器地址，默认为城通网盘
        """
        self.base_url = base_url.rstrip('/') + '/'
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'CloudSync/2.2 (Python WebDAV Client)'
        })
        self.is_authenticated = False
        self.auth_header = None
        self.email = None
        
        # 针对小文件(<64MB)优化，使用直接上传方式
        self.max_file_size = 64 * 1024 * 1024  # 64MB
        
    def authenticate(self, email: str, password: str, timeout: int = 30) -> bool:
        """
        认证用户
        
        Args:
            email: 邮箱地址
            password: 密码
            timeout: 请求超时时间(秒)
            
        Returns:
            bool: 认证是否成功
        """
        try:
            self.email = email
            auth_str = f"{email}:{password}"
            auth_b64 = base64.b64encode(auth_str.encode()).decode()
            self.auth_header = f"Basic {auth_b64}"
            
            # 测试认证有效性
            headers = {'Authorization': self.auth_header}
            response = self.session.request(
                'PROPFIND',
                self.base_url,
                headers=headers,
                timeout=timeout
            )
            
            if response.status_code in [200, 207]:
                self.is_authenticated = True
                print(f"✅ 认证成功! 用户: {email}")
                return True
            else:
                print(f"❌ 认证失败: HTTP {response.status_code}")
                return False
                
        except Exception as e:
            print(f"❌ 认证过程中发生错误: {str(e)}")
            return False
    
    def _check_file_size(self, file_path: str) -> bool:
        """
        检查文件大小是否超过限制
        
        Args:
            file_path: 文件路径
            
        Returns:
            bool: 文件大小是否有效
        """
        file_size = os.path.getsize(file_path)
        if file_size > self.max_file_size:
            print(f"❌ 文件大小超过限制: {file_size} > {self.max_file_size}")
            return False
        return True
    
    def download_file(self, 
                     remote_path: str, 
                     local_path: str, 
                     overwrite: bool = True) -> bool:
        """
        下载远程文件到本地(直接下载方式)
        
        Args:
            remote_path: 远程文件路径
            local_path: 本地保存路径
            overwrite: 是否覆盖已存在的文件
            
        Returns:
            bool: 下载是否成功
        """
        if not self.is_authenticated:
            print("❌ 请先进行认证!")
            return False
            
        try:
            # 检查本地文件是否存在
            local_file = Path(local_path)
            if local_file.exists() and not overwrite:
                print(f"⚠️ 本地文件已存在: {local_path}")
                return True
            
            # 构建远程URL
            remote_url = urljoin(self.base_url, remote_path.lstrip('/'))
            headers = {'Authorization': self.auth_header}
            
            # 直接下载整个文件
            response = self.session.get(remote_url, headers=headers)
            
            if response.status_code == 200:
                # 确保本地目录存在
                local_file.parent.mkdir(parents=True, exist_ok=True)
                
                # 直接写入文件
                with open(local_path, 'wb') as file:
                    file.write(response.content)
                
                file_size = len(response.content)
                print(f"✅ 文件下载成功: {local_path} ({file_size} bytes)")
                return True
            else:
                print(f"❌ 下载失败: HTTP {response.status_code}")
                return False
                
        except Exception as e:
            print(f"❌ 下载过程中发生错误: {str(e)}")
            return False
    
    def upload_file(self, 
                   local_path: str, 
                   remote_path: str,
                   overwrite: bool = True) -> bool:
        """
        上传本地文件到远程(直接上传方式)
        
        Args:
            local_path: 本地文件路径
            remote_path: 远程保存路径
            overwrite: 是否覆盖远程已存在文件
            
        Returns:
            bool: 上传是否成功
        """
        if not self.is_authenticated:
            print("❌ 请先进行认证!")
            return False
            
        try:
            local_file = Path(local_path)
            if not local_file.exists():
                print(f"❌ 本地文件不存在: {local_path}")
                return False
            
            # 检查文件大小
            if not self._check_file_size(local_path):
                return False
            
            # 检查远程文件是否存在
            if not overwrite and self.file_exists(remote_path):
                print(f"⚠️ 远程文件已存在: {remote_path}")
                return True
            
            # 构建远程URL
            remote_url = urljoin(self.base_url, remote_path.lstrip('/'))
            headers = {'Authorization': self.auth_header}
            
            # 直接读取整个文件并上传
            with open(local_path, 'rb') as file:
                file_data = file.read()
                response = self.session.put(remote_url, data=file_data, headers=headers)
            
            if response.status_code in [200, 201, 204]:
                file_size = len(file_data)
                print(f"✅ 文件上传成功: {remote_path} ({file_size} bytes)")
                return True
            else:
                print(f"❌ 上传失败: HTTP {response.status_code}")
                return False
                
        except Exception as e:
            print(f"❌ 上传过程中发生错误: {str(e)}")
            return False
    
    def list_directory(self, remote_path: str = "") -> Optional[List[Dict]]:
        """
        列出远程目录内容
        
        Args:
            remote_path: 远程目录路径
            
        Returns:
            list: 包含文件信息的字典列表
        """
        if not self.is_authenticated:
            print("❌ 请先进行认证!")
            return None
            
        try:
            remote_url = urljoin(self.base_url, remote_path.lstrip('/'))
            headers = {
                'Authorization': self.auth_header,
                'Depth': '1'
            }
            
            # 发送PROPFIND请求获取目录列表
            response = self.session.request('PROPFIND', remote_url, headers=headers)
            
            if response.status_code in [200, 207]:
                return self._parse_webdav_response(response.text)
            else:
                print(f"❌ 获取目录列表失败: HTTP {response.status_code}")
                return None
                
        except Exception as e:
            print(f"❌ 获取目录列表时发生错误: {str(e)}")
            return None
    
    def _parse_webdav_response(self, xml_content: str) -> List[Dict]:
        """解析WebDAV XML响应"""
        try:
            root = ET.fromstring(xml_content)
            namespace = {'d': 'DAV:'}
            items = []
            
            for response in root.findall('.//d:response', namespace):
                href = response.find('d:href', namespace)
                propstat = response.find('d:propstat', namespace)
                
                if href is not None and propstat is not None:
                    prop = propstat.find('d:prop', namespace)
                    if prop is not None:
                        # 获取显示名称
                        display_name_elem = prop.find('d:displayname', namespace)
                        display_name = display_name_elem.text if display_name_elem is not None else href.text.split('/')[-1]
                        
                        item = {
                            'href': href.text,
                            'display_name': display_name,
                            'is_directory': prop.find('d:resourcetype/d:collection', namespace) is not None,
                            'content_length': getattr(prop.find('d:getcontentlength', namespace), 'text', '0'),
                            'last_modified': getattr(prop.find('d:getlastmodified', namespace), 'text', ''),
                            'etag': getattr(prop.find('d:getetag', namespace), 'text', '')
                        }
                        items.append(item)
            
            return items
        except Exception as e:
            print(f"❌ 解析WebDAV响应失败: {str(e)}")
            return []
    
    def file_exists(self, remote_path: str) -> bool:
        """
        检查远程文件是否存在
        
        Args:
            remote_path: 远程文件路径
            
        Returns:
            bool: 文件是否存在
        """
        if not self.is_authenticated:
            return False
            
        try:
            remote_url = urljoin(self.base_url, remote_path.lstrip('/'))
            headers = {'Authorization': self.auth_header}
            
            response = self.session.head(remote_url, headers=headers)
            return response.status_code == 200
        except:
            return False
    
    def get_file_info(self, remote_path: str) -> Optional[Dict[str, Any]]:
        """
        获取远程文件信息
        
        Args:
            remote_path: 远程文件路径
            
        Returns:
            dict: 文件信息字典
        """
        if not self.is_authenticated:
            return None
            
        try:
            remote_url = urljoin(self.base_url, remote_path.lstrip('/'))
            headers = {'Authorization': self.auth_header}
            
            response = self.session.head(remote_url, headers=headers)
            
            if response.status_code == 200:
                return {
                    'size': int(response.headers.get('Content-Length', 0)),
                    'last_modified': response.headers.get('Last-Modified', ''),
                    'etag': response.headers.get('ETag', ''),
                    'content_type': response.headers.get('Content-Type', '')
                }
            return None
        except Exception as e:
            print(f"❌ 获取文件信息失败: {str(e)}")
            return None
    
    def create_directory(self, remote_path: str) -> bool:
        """
        创建远程目录
        
        Args:
            remote_path: 远程目录路径
            
        Returns:
            bool: 创建是否成功
        """
        if not self.is_authenticated:
            print("❌ 请先进行认证!")
            return False
            
        try:
            # 确保路径格式正确
            if not remote_path.endswith('/'):
                remote_path += '/'
                
            remote_url = urljoin(self.base_url, remote_path.lstrip('/'))
            headers = {'Authorization': self.auth_header}
            
            response = self.session.request('MKCOL', remote_url, headers=headers)
            
            if response.status_code in [200, 201]:
                print(f"✅ 目录创建成功: {remote_path}")
                return True
            elif response.status_code in [405, 409]:  # 405=已存在, 409=冲突(可能已存在)
                print(f"⚠️ 目录已存在: {remote_path}")
                return True
            else:
                print(f"❌ 目录创建失败: HTTP {response.status_code}")
                return False
        except Exception as e:
            print(f"❌ 创建目录时发生错误: {str(e)}")
            return False
    
    def delete_file(self, remote_path: str) -> bool:
        """
        删除远程文件或目录
        
        Args:
            remote_path: 远程路径
            
        Returns:
            bool: 删除是否成功
        """
        if not self.is_authenticated:
            print("❌ 请先进行认证!")
            return False
            
        try:
            remote_url = urljoin(self.base_url, remote_path.lstrip('/'))
            headers = {'Authorization': self.auth_header}
            
            response = self.session.delete(remote_url, headers=headers)
            
            if response.status_code in [200, 204]:
                print(f"✅ 删除成功: {remote_path}")
                return True
            else:
                print(f"❌ 删除失败: HTTP {response.status_code}")
                return False
        except Exception as e:
            print(f"❌ 删除过程中发生错误: {str(e)}")
            return False
    
    def calculate_file_hash(self, file_path: str) -> str:
        """
        计算文件MD5哈希值
        
        Args:
            file_path: 文件路径
            
        Returns:
            str: 文件的MD5哈希值
        """
        hash_md5 = hashlib.md5()
        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(4096), b""):
                hash_md5.update(chunk)
        return hash_md5.hexdigest()
    
    def disconnect(self):
        """
        断开连接并清理资源
        """
        self.is_authenticated = False
        self.auth_header = None
        self.email = None
        self.session.close()
        print("✅ 已断开连接")


# =============================================================================
# 示例和使用案例
# =============================================================================

def example_basic_usage():
    """基础使用示例"""
    print("=" * 50)
    print("CloudSync 基础使用示例")
    print("=" * 50)
    
    # 创建客户端实例
    client = CloudSync()
    
    try:
        # 认证登录 - 注意：实际使用时密码应由用户输入，此处仅为示例
        if client.authenticate("12@dingtalk.com", "123456yz"):
            
            # 创建测试目录
            client.create_directory("test_files")
            
            # 上传文件
            with open("test_upload.txt", "w", encoding="utf-8") as f:
                f.write("这是测试上传的文件内容")
            
            success = client.upload_file("test_upload.txt", "test_files/uploaded.txt")
            
            if success:
                # 下载文件
                client.download_file("test_files/uploaded.txt", "test_download.txt")
                
                # 列出目录内容
                files = client.list_directory("test_files")
                if files:
                    print("📁 目录内容:")
                    for file in files:
                        file_type = "目录" if file['is_directory'] else "文件"
                        print(f"  - {file['display_name']} ({file_type}, {file['content_length']} bytes)")
                
                # 清理测试文件
                client.delete_file("test_files/uploaded.txt")
            
            # 清理本地文件
            if os.path.exists("test_upload.txt"):
                os.remove("test_upload.txt")
            if os.path.exists("test_download.txt"):
                os.remove("test_download.txt")
            
    except Exception as e:
        print(f"示例执行出错: {e}")
    finally:
        # 断开连接
        client.disconnect()


def example_file_management():
    """文件管理操作示例"""
    print("\n" + "=" * 50)
    print("CloudSync 文件管理示例")
    print("=" * 50)
    
    client = CloudSync()
    
    try:
        # 认证登录 - 注意：实际使用时密码应由用户输入，此处仅为示例
        if client.authenticate("12@dingtalk.com", "123456yz"):
            # 检查文件是否存在
            exists = client.file_exists("some_file.txt")
            print(f"文件存在: {exists}")
            
            # 创建嵌套目录
            client.create_directory("test_parent")
            client.create_directory("test_parent/test_child")
            
            # 创建测试文件并上传
            test_file = "hash_test.txt"
            with open(test_file, "w", encoding="utf-8") as f:
                f.write("测试哈希值的内容")
            
            # 上传文件
            client.upload_file(test_file, "test_parent/test_child/hash_test.txt")
            
            # 获取文件信息
            info = client.get_file_info("test_parent/test_child/hash_test.txt")
            if info:
                print(f"文件信息: {info}")
            
            # 计算文件哈希
            file_hash = client.calculate_file_hash(test_file)
            print(f"文件哈希值: {file_hash}")
            
            # 清理
            client.delete_file("test_parent/test_child/hash_test.txt")
            client.delete_file("test_parent/test_child")
            client.delete_file("test_parent")
            os.remove(test_file)
            
    except Exception as e:
        print(f"示例执行出错: {e}")
    finally:
        client.disconnect()


def example_error_handling():
    """错误处理示例"""
    print("\n" + "=" * 50)
    print("CloudSync 错误处理示例")
    print("=" * 50)
    
    client = CloudSync()
    
    try:
        # 尝试未认证的操作
        print("尝试未认证的操作:")
        client.download_file("nonexistent.txt", "test.txt")
        
    except Exception as e:
        print(f"预期中的错误: {e}")
    
    try:
        # 使用错误凭证
        print("使用错误凭证:")
        client.authenticate("wrong@email.com", "wrong_password")
        
    except Exception as e:
        print(f"认证错误: {e}")
    
    finally:
        client.disconnect()


def example_real_world_usage():
    """真实世界使用示例 - 应用程序数据同步"""
    print("\n" + "=" * 50)
    print("CloudSync 真实世界使用示例")
    print("=" * 50)
    
    # 模拟应用程序数据同步场景
    client = CloudSync()
    
    try:
        # 在实际应用中，密码应该由用户输入或从配置文件中读取
        # 这里仅为演示目的使用示例密码
        email = "12@dingtalk.com"  # 实际应用中应该由用户输入
        password = "123456yz"  # 实际应用中应该由用户输入
        
        if client.authenticate(email, password):
            # 模拟应用程序数据文件
            app_data = {
                "config": {"theme": "dark", "language": "zh-CN"},
                "user_preferences": {"notifications": True, "auto_save": True},
                "cache_data": {"last_update": time.strftime("%Y-%m-%d"), "version": "1.0.0"}
            }
            
            # 保存本地配置文件
            with open("app_config.json", "w", encoding="utf-8") as f:
                json.dump(app_data, f, indent=2, ensure_ascii=False)
            
            # 上传到云端备份
            print("📤 备份应用程序配置...")
            if client.upload_file("app_config.json", "my_app/backup/config.json"):
                print("✅ 配置备份成功!")
                
                # 从云端恢复配置
                print("📥 恢复应用程序配置...")
                if client.download_file("my_app/backup/config.json", "restored_config.json"):
                    with open("restored_config.json", "r", encoding="utf-8") as f:
                        restored_data = json.load(f)
                    print(f"✅ 配置恢复成功! 版本: {restored_data['cache_data']['version']}")
            
            # 清理临时文件
            for temp_file in ["app_config.json", "restored_config.json"]:
                if os.path.exists(temp_file):
                    os.remove(temp_file)
                
    except Exception as e:
        print(f"示例执行出错: {e}")
    finally:
        client.disconnect()


def example_small_file_upload():
    """小文件上传测试示例"""
    print("\n" + "=" * 50)
    print("CloudSync 小文件上传测试")
    print("=" * 50)
    
    client = CloudSync()
    
    try:
        if client.authenticate("12@dingtalk.com", "123456yz"):
            # 创建不同大小的小文件进行测试
            file_sizes = [1, 10, 100, 1024]  # KB
            
            for size_kb in file_sizes:
                filename = f"test_{size_kb}kb.dat"
                remote_path = f"test_files/{filename}"
                
                # 生成测试文件
                with open(filename, "wb") as f:
                    f.write(os.urandom(size_kb * 1024))
                
                print(f"测试上传 {size_kb}KB 文件...")
                if client.upload_file(filename, remote_path):
                    print(f"✅ {size_kb}KB 文件上传成功")
                    
                    # 验证下载
                    download_name = f"downloaded_{size_kb}kb.dat"
                    if client.download_file(remote_path, download_name):
                        # 验证文件大小
                        original_size = os.path.getsize(filename)
                        downloaded_size = os.path.getsize(download_name)
                        
                        if original_size == downloaded_size:
                            print(f"✅ 文件大小验证成功: {original_size} bytes")
                        else:
                            print(f"❌ 文件大小不匹配: {original_size} vs {downloaded_size}")
                        
                        # 清理下载的文件
                        os.remove(download_name)
                    
                    # 清理远程文件
                    client.delete_file(remote_path)
                
                # 清理本地文件
                os.remove(filename)
                
    except Exception as e:
        print(f"示例执行出错: {e}")
    finally:
        client.disconnect()


if __name__ == "__main__":
    """
    CloudSync 库完整示例
    
    运行此文件将执行所有示例用例
    """
    
    print("🚀 CloudSync 简化版库演示开始")
    print("注意: 示例中使用的是示例账户，实际使用时请替换为您的账户信息")
    print("本版本针对小文件(<50MB)优化，使用直接上传方式")
    print()
    
    # 执行所有示例
    examples = [
        example_basic_usage,
        example_file_management,
        example_error_handling,
        #example_real_world_usage,
        example_small_file_upload
    ]
    
    for example in examples:
        try:
            example()
            print()
            time.sleep(1)  # 示例间暂停
        except Exception as e:
            print(f"示例执行失败: {e}")
            print()
    
    print("✅ 所有示例演示完成!")
    print("\n📚 快速开始:")
    print("1. from cloudsync import CloudSync")
    print("2. client = CloudSync()")
    print("3. client.authenticate('your_email@example.com', 'your_password')")
    print("4. client.upload_file('local.txt', 'remote.txt')  # 直接上传")
    print("5. client.disconnect()")